/**
 * TODO: Vložit licenci.
 */

package cz.enif.commander;

import cz.enif.commander.sources.ISource;

/**
 * Slouží ke čtení scriptu ze zadaného zdroje znak po znaku.
 * Určení typu znaku podle jazyka Commanderu (číslice, písmeno atd.).
 *
 * @author Přemysl Fára
 */
final class Scanner
{
    //// CONSTRUCTORS ////

    /**
     * Konstruktor.
     */
    public Scanner(ISource source)
    {
    	this.source = source;
        this.line = 0;
        
       	this.nextChar(); // načteme první znak
    }

    //// PUBLIC ////

    /**
     * Načte další znak ze scriptu, uloží jej do proměnné look.
     */
    public void nextChar()
    {
        if (this.source == null)
        {
            this.look = -1;
        }
        else
        {
            if (this.isEOLN())
            {
                this.line++;
            }

            this.look = this.source.next();
        }
    }

    /**
     * Vrací naposledy přečtený znak.
     *
     *  @return Naposledy přečtený znak.
     */
    public int getLook()
    {
        return this.look;
    }

    /**
     * Vrací číslo aktuální řádky, počínaje 0.
     *
     *  @return Číslo aktuální řádky.
     */
    public int getLine()
    {
        return this.line;
    }

    /**
     * Vrací true, pokud je znak c shodný s naposledy čteným znakem.
     *
     *  @param c Znak, se kterým porovnáváme naposledy čtený znak.
     *
     *  @return True, pokud je znak c shodný s naposledy čteným znakem.
     */
    public boolean is(int c)
    {
        return this.look == c;
    }

    /**
     * Vrací true, pokud poslední čtený znak byl konec řádku.
     *
     *  @return True, pokud poslední čtený znak byl konec řádku.
     */
    public boolean isEOF()
    {
        return this.look == -1;
    }

    /**
     * Vrací true, pokud je znak c shodný s naposledy čteným znakem.
     *
     *  @param c Znak, se kterým porovnáváme naposledy čtený znak.
     *
     *  @return True, pokud je znak c shodný s naposledy čteným znakem.
     */
    public boolean isEOLN()
    {
        return this.look == '\n';
    }

    /**
     * Vrací true, pokud je znak c různý od naposledy čteného znaku.
     *
     *  @param c Znak, se kterým porovnáváme naposledy čtený znak.
     *
     *  @return True, pokud je znak c různý od naposledy čteného znaku.
     */
    public boolean isNot(int c)
    {
        return this.look != c;
    }

    /**
     * Vrací true ještě nejsme na konci vstupu.
     *
     *  @return True, pokud nejsme na konci vstupu.
     */
    public boolean isNotEOF()
    {
        return this.look != -1;
    }

    /**
     * Vrací true, pokud poslední čtený znak nebyl konec řádku.
     *
     *  @return True, pokud poslední čtený znak nebyl konec řádku.
     */
    public boolean isNotEOLN()
    {
        return this.look != '\n';
    }

    /**
     * Vrací true, pokud je naposledy čtený znak bílý znak.
     *
     *  @return True, pokud je naposledy čtený znak bílý znak.
     */
    public boolean isWhite()
    {
        return Scanner.isWhite(this.look);
    }

    /**
     * Vrací true, pokud je naposledy čtený znak nečíselný (písmeno, podtržítko).
     *
     *  @return True, pokud je naposledy čtený znak nečíselný.
     */
    public boolean isNonDigit()
    {
        return Scanner.isNonDigit(this.look);
    }

    /**
     * Vrací true, pokud je naposledy čtený znak číslice.
     *
     *  @return True, pokud je naposledy čtený znak číslice.
     */
    public boolean isDigit()
    {
        return Scanner.isDigit(this.look);
    }

    /**
     * Vrací true, pokud je naposledy čtený znak hexadecimální číslice.
     *
     *  @return True, pokud je naposledy čtený znak hexadecimální číslice.
     */
    public boolean isHexDigit()
    {
        return Scanner.isHexDigit(this.look);
    }

    //// STATIC ////

    /**
     * Defines white-space characters recognized by the language.
     *
     * Returns 1 (true), if the given character is a white-space. If it
     * is not a white-space, returns 0 (false).
     *
     * Note: 0xA0 = no-break space (NBSP, see ECMAScript 3.0)
     *
     * <pre>
     * white-space :: ' ' | '\\t' | '\\f' | '\\xA0' | '\\r' .
     * </pre>
     *
     */
    public static boolean isWhite(int c)
    {
        if (c == ' ' || c == '\t' || c == '\r' || c == '\b' || c == '\f' || c == 0xA0)
        {
            return true;
        }

        return false;
    }

    /**
    * Defines non-digit characters recognized by the language.
    *
    * Returns 1 (true), if the given character is a non-digit. If it
    * is not a non-digit, returns 0 (false). For example, identifiers
    * begins and can contain non-digits (and digits also).
    *
    * <pre>
    * non-digit ::
    *   '_' |
    *   'a' .. 'z' | 'A' .. 'Z' .
    * </pre>
    *
    */
    public static boolean isNonDigit(int c)
    {
        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'))
        {
            return true;
        }

        return false;
    }

    /**
     * Defines digit characters recognized by the language.
     *
     * Returns 1 (true), if the given character is a digit. If it
     * is not a digit, returns 0 (false).
     *
     * <pre>
     * digit ::
     *   '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' .
     * </pre>
     *
     */
    public static boolean isDigit(int c)
    {
        if ((c >= '0' && c <= '9'))
        {
            return true;
        }

        return false;
    }

    /**
     * Defines hexadecimal-digit characters recognized by the language.
     *
     * Returns 1 (true), if the given character is a hexadecimal-digit. If it
     * is not a hexadecimal-digit, returns 0 (false).
     *
     * <pre>
     * hexadecimal-digit ::
     *   digit |
     *   'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
     *   'A' | 'B' | 'C' | 'D' | 'E' | 'F' .
     * </pre>
     *
     */

    public static boolean isHexDigit(int c)
    {
        if ( (c >= '0' && c <= '9') ||
           (c >= 'a' && c <= 'f') ||
           (c >= 'A' && c <= 'F'))
        {

            return true;
        }

        return false;
    }

    //// PRIVATE ////

    private ISource source;
    
    private int look;
    private int line;
}

